/*
 * @file   Scheduler.java
 * @author Enrique Madrid <e.madrid@lpcsite.com>
 * @date   Mon Jul 16 14:01:55 2007
 *
 * @brief  Adninistrador de alarmas.
 *
 */

package sim.framework;

import java.io.*;
import java.util.*;
import sim.utils.*;


public class Scheduler extends Thread  {

    private static final int QUEUE_INIT_SIZE = 16;

    private static final byte LOCKED_FOR_TEST = 1;
    private static final byte LOCKED_FOR_INIT = 2;
    private static final byte LOCKED_FOR_KILL = 3;
    
    private Simlet m_Simlet;
    private Vector m_Alarms = new Vector(QUEUE_INIT_SIZE);
    private boolean m_Stop;

    private long m_MCD;
    private int m_MyHashCode;

    private Msg m_Msg = null;
    private EventListener m_EventListener;
    
    public Scheduler(Simlet s) {
        m_Simlet = s;
	m_Msg = new Msg();
        m_Msg.setEventCode(EventListener.ME_ALARM);
    }
    
    public void run() {
        long period;

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::run() ENTERED");
	//#endif
        
        m_MyHashCode = this.hashCode();
        
        while( !m_Stop ) {
	    //#ifdef DEBUG_SCHEDULER
	    //# System.out.println("\tScheduler::run() iterando");
	    //#endif	    

	    while (testAlarm()) {
		try {
		    m_EventListener.event(m_Msg);
		}
		catch (Exception e) {
		    System.out.println("\nME CAI EN SCHEDULER\n");
		    System.out.println("\nMesage\n");
		    e.getMessage();
		    System.out.println("\nTrace\n");
		    e.printStackTrace();		   
		}
	    }

	    //#ifdef DEBUG_SCHEDULER
	    //# System.out.println("\tScheduler::run() durmiendo");
	    //#endif	    
	    synchronized(this) {
		try {
		    wait(m_MCD);
		} catch(InterruptedException e ) {
		    
		}
	    }
        }

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::run() EXITING");
	//#endif
        
    }
        
    private Alarm newAlarm() {
	Enumeration e;
        Alarm alarm;

	e = m_Alarms.elements();
	while (e.hasMoreElements()) {
	    alarm = (Alarm)e.nextElement();
	    if (alarm.isDisabled()) {
		return alarm;
	    }
	}

        alarm = new Alarm();
        m_Alarms.addElement(alarm);
        return alarm;
    }
    
    private Alarm getAlarm(int id) {
	Enumeration e;
        Alarm alarm;

	e = m_Alarms.elements();
	while (e.hasMoreElements()) {
	    alarm = (Alarm)e.nextElement();
	    if (alarm.m_Id == id) {
		return alarm;
	    }
	}

	return null;
    }

    private void calcMCD() {
	Enumeration e;
        Alarm alarm;
        
        // Si no hay alarmas el MCD es cero
        m_MCD = 0;

	// Buscar la primera alarma activa
	e = m_Alarms.elements();
	while (e.hasMoreElements()) {
	    alarm = (Alarm)e.nextElement();
	    if (!alarm.isDisabled()) {
                m_MCD = alarm.m_Period;
		break;
	    }
	}

	// Calcular el MCD con todas las alarmas
	if (m_MCD > 0) {
	    e = m_Alarms.elements();
	    while (e.hasMoreElements()) {
		alarm = (Alarm)e.nextElement();
		if (!alarm.isDisabled()) {
		    m_MCD = (long)Utils.calcMCD((int)m_MCD, (int)alarm.m_Period);
		}
	    }
	}
    }

    private synchronized long lock(int lockFor, int id, long periode, 
				   int param , EventListener e) {
	long ret = 0;
	switch(lockFor) {
	case Scheduler.LOCKED_FOR_INIT:
	    ret = lockedForInit(id, periode, param, e);
	    break;
	case Scheduler.LOCKED_FOR_KILL:
	    ret = lockedForKill(id);
	    break;
	case Scheduler.LOCKED_FOR_TEST:
	    ret = lockedForTest2();
	    break;
	}
	return ret;
    }
    
    private long lockedForInit(int id, long periode, int param, EventListener e) {
        Alarm alarm;
	long ret;

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForInit() ENTERED");
	//#endif

	ret = 0;
	// Si la alarma no existe o es un pulso ...
	if ((getAlarm(id) == null) || (periode == 0)) {
	    ret = 1;
	    // ... iniciar a la alarma.
	    alarm = newAlarm();
	    alarm.set(id, periode, param, e);       
	    // Calcular el MCD si la alarma no es un pulso
	    if (!alarm.isPulse()) {
		calcMCD();
	    }
	    notify();
	}
	
	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForInit() EXITING");
	//#endif
	return ret;
    }
    
    private long lockedForKill(int id) {
        Alarm alarm;
	boolean pulse;
	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForKill() ENTERED");
	//#endif

	// Borrar alarmas
	alarm = getAlarm(id);
	if (alarm != null) {
	    pulse = alarm.isPulse();
	    alarm.disable();
	    // Calcular el MCD si la alarma no es un pulso
	    if (!pulse) {
		calcMCD();
	    }
	    notify();
	}	
	
	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForKill() EXITING");
	//#endif	
	return 0;
    }

    private long lockedForTest() {
	Enumeration e;
        Alarm alarm;
        long initTime;
        long currTime;
	long ret;

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForTest() ENTERED");
	//#endif

	ret = m_MCD;

	initTime = System.currentTimeMillis();
	e = m_Alarms.elements();
	while(e.hasMoreElements()) {
	    alarm = (Alarm)e.nextElement();
	    if(alarm.isTimeout(System.currentTimeMillis())) {
		alarm.wakeUp(m_Msg);
		// Si la alarma es un pulso ...
		if(alarm.isPulse()) {
		    // ... deshabilitarlo
		    alarm.disable();
		}
	    }
	    
	}
	currTime = System.currentTimeMillis();

	if (m_MCD > 0) {
	    ret = m_MCD - (currTime - initTime);
	}

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("\tTiempo a domir : " + ret);
	//#endif

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForTest() EXITING");
	//#endif
	return ret;
    }    

    private long lockedForTest2() {
	Enumeration e;
        Alarm alarm;
        long initTime;
        long currTime;
	long ret;

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForTest() ENTERED");
	//#endif

	ret = 0;
	e = m_Alarms.elements();
	while(e.hasMoreElements()) {
	    alarm = (Alarm)e.nextElement();
	    if(alarm.isTimeout(System.currentTimeMillis())) {
		ret = 1;
		m_EventListener = alarm.m_EventListener;
		m_Msg.setWParam(alarm.m_Id);
		m_Msg.setDWParam(alarm.m_Param);
		// Si la alarma es un pulso ...
		if(alarm.isPulse()) {
		    // ... deshabilitarlo
		    alarm.disable();
		}
	    }
	    
	}

	//#ifdef DEBUG_SCHEDULER
	//# System.out.println("Scheduler::lockedForTest() EXITING");
	//#endif
	return ret;
    }

    public boolean initAlarm(int id, long period, int param , EventListener e) {
	Thread t;
	boolean ret;       
	t = currentThread();
        if (t.hashCode() != m_MyHashCode) {
	    ret = (lock(Scheduler.LOCKED_FOR_INIT, id, period, param, e) == 0);
        } else {
	    ret = (lockedForInit(id, period, param, e) == 0);
        }
	return ret;
    }
    
    public void killAlarm(int id) {
	Thread t;
	boolean ret;       
	t = currentThread();
        if (t.hashCode() != m_MyHashCode) {
	    lock(Scheduler.LOCKED_FOR_KILL, id, 0, 0, null);
        } else {
	    lockedForKill(id);
        }
    }
    
//     private long testAlarm() {
//         return lock(Scheduler.LOCKED_FOR_TEST, 0, 0, 0, null);
//     }

    private boolean testAlarm() {
        return (lock(Scheduler.LOCKED_FOR_TEST, 0, 0, 0, null) == 1);
    }
        
    protected synchronized void stop() {
        m_Stop = true;
        notify();
    }   
}
